/*********************************************************************************
 *      Copyright:  (C) 2017 TangBin<tangbinmvp@gmail.com>
 *                  All rights reserved.
 *
 *       Filename:  adc.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(04/07/2017)
 *         Author:  TangBin <tangbinmvp@gmail.com>
 *      ChangeLog:  1, Release initial version on "04/07/2017 12:48:02 PM"
 *                 
 ********************************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <plat/regs-adc.h>
#include <linux/errno.h>
#include <asm/uaccess.h>    //copy_to_user、copy_from_user
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <linux/ioport.h>

#define DEVICE_NAME "S3C_ADC"
#define S3C_ADC_MAJOR   0 
#define ENABLE      1
#define DISABLE     0

#define CON_BASE    0x58000000
#define DATA_BASE   0x5800000c
#define CON_LEN     4
#define DATA_LEN    4
#define s3c_adc_read(reg)       __raw_readl((reg))
#define s3c_adc_write(val,reg)  __raw_writel((val),(reg))

static void __iomem *adc_con;
static void __iomem *adc_dat0;  /* 保存经过虚拟映射后的内存地址 */

static struct clk *adc_clk;     /* 保存从平台时钟队列中获取ADC的时钟 */
DEFINE_MUTEX(ADC_CLK);          /* 定义互斥锁 */

int dev_cnt = 1;
int dev_major = S3C_ADC_MAJOR;
int dev_minor = 0;
int debug = DISABLE;

static struct cdev *adc_cdev;

static int adc_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);

    file->private_data = (void *)minor;
    printk(KERN_DEBUG "dev/s3c_adc %d opened.\n", minor);

    return 0;
}

static ssize_t adc_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
    unsigned int tmp;
    unsigned long adc_data;

    adc_con = ioremap(CON_BASE, CON_LEN);
    adc_dat0 = ioremap(DATA_BASE, DATA_LEN);

    tmp = s3c_adc_read(adc_con);
    tmp = (1 << 14) | (255 << 6) | (0 << 3);
    s3c_adc_write(tmp, adc_con); 

    tmp = s3c_adc_read(adc_con);
    tmp = tmp | (1 << 0);
    s3c_adc_write(tmp, adc_con);    //启动AD转换

    while(s3c_adc_read(adc_con) &0x1); //启动转换后，等待启动位清零
    while(!(s3c_adc_read(adc_con) &0x8000));    //等待是否转换完毕

    mutex_lock(&ADC_CLK);
    adc_data = s3c_adc_read(adc_dat0);
    copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
    mutex_unlock(&ADC_CLK);

    return sizeof(adc_data);
}

static int adc_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations adc_fops = {
    .owner = THIS_MODULE,
    .open = adc_open,
    .read = adc_read,
    .release = adc_release,
};

static int __init adc_init(void)
{
    int result;
    dev_t devno;
    
    /* 分配主次设备号 */
    if(0 != dev_major)  
    {
        devno = MKDEV(dev_major, 0);
        result = register_chrdev_region(devno, dev_cnt, DEVICE_NAME);
    }
    else
    {
        result = alloc_chrdev_region(&devno, dev_minor, dev_cnt, DEVICE_NAME);
        dev_major = MAJOR(devno);
    }
    if(result < 0)
    {
        printk(KERN_ERR "%s driver can't use major %d\n", DEVICE_NAME, dev_major);
        return -ENODEV;
    }

    printk(KERN_DEBUG "%s driver major %d\n", DEVICE_NAME, dev_major);

    /* 从平台时钟队列中获取ADC的时钟 */
    adc_clk = clk_get(NULL, "s3c_adc");
    if(!adc_clk)
    {
         printk(KERN_ERR "failed to find adc clock source\n");
         return -ENOENT;
    }
    clk_enable(adc_clk);

    if(NULL == (adc_cdev=cdev_alloc()))
    {
        printk(KERN_ERR "%s driver can't alloc for the cdev.\n", DEVICE_NAME);
        unregister_chrdev_region(devno, dev_cnt);
        return -ENOMEM; 
    }
    
    adc_cdev->owner = THIS_MODULE;
    cdev_init(adc_cdev, &adc_fops);

    result = cdev_add(adc_cdev, devno, dev_cnt);
    if(0 != result)
    {
        printk(KERN_INFO "%s driver can't reigster cdev: result=%d\n", DEVICE_NAME, result);
        goto ERROR;
    }
    printk(KERN_ERR "%s installed successfully.\n",DEVICE_NAME);

    return 0;

ERROR:
    printk(KERN_ERR "%s driver installed failure.\n", DEVICE_NAME);
    cdev_del(adc_cdev);
    unregister_chrdev_region(devno, dev_cnt);
    return result;
}

void __exit adc_exit(void)
{
    dev_t devno = MKDEV(dev_major, dev_minor);
    cdev_del(adc_cdev);

    unregister_chrdev_region(devno, dev_cnt);

    iounmap(adc_con);
    iounmap(adc_dat0);

    if(adc_clk)
    {
        clk_disable(adc_clk);
        clk_put(adc_clk);
        adc_clk = NULL;
    }
    printk(KERN_ERR " %s driver removed!\n", DEVICE_NAME);

    return ; 

}
module_init(adc_init);
module_exit(adc_exit);

module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ADC Driver");
